For Tool Callers
This guide helps you discover and use UTCP tools in your applications, whether you’re building an AI agent, a workflow automation system, or any other tool-consuming application.
The Discovery Process
Using tools with UTCP follows this simple process:
- Connect to a tool provider’s discovery endpoint
- Retrieve the UTCPManual containing available tools
- Register the tools in your client
- Call tools as needed using their native protocols
Using the UTCP Client
The utcp
package provides a ready-to-use client for discovering and calling tools. You can configure it with variables and even auto-load providers:
import asyncio
from utcp import UtcpClient
from utcp.models import Provider
async def main():
# Create a UTCP client
client = UtcpClient()
# Define the provider
provider = HttpProvider(
name="weather_api",
provider_type="http",
url="https://api.example.com/utcp",
http_method="GET"
)
# Register tools from the provider
tools = await client.register_tool_provider(provider)
print(f"Registered {len(tools)} tools from {provider.name}")
# Call a tool with arguments
result = await client.call_tool("weather_api.get_weather", arguments={"location": "San Francisco"})
print(f"Weather: {result['temperature']}°C, {result['conditions']}")
if __name__ == "__main__":
asyncio.run(main())
Tool Namespacing
UTCP uses a simple namespacing convention to avoid name conflicts:
provider_name.tool_name
For example, if a provider named “weather_api” offers a tool named “get_forecast”, you would call it using weather_api.get_forecast
.
Understanding Tool Definitions
When you register a provider, you receive tool definitions that include:
- Name and Description: What the tool is and what it does
- Input Schema: The parameters the tool accepts
- Output Schema: The structure of the tool’s response
- Provider Information: How to call the tool directly
Here’s what a typical tool definition looks like:
{
"name": "get_weather",
"description": "Get current weather for a location",
"inputs": {
"type": "object",
"properties": { "location": { "type": "string", "description": "City name" } },
"required": ["location"]
},
"outputs": {
"type": "object",
"properties": { "temperature": {"type": "number"}, "conditions": {"type": "string"} }
},
"tool_provider": {
"name": "weather_api",
"provider_type": "http",
"url": "https://api.example.com/api/weather",
"http_method": "GET"
}
}
Error Handling Best Practices
When working with UTCP tools, implement these error handling strategies:
Provider Registration Errors:
- Retry with exponential backoff for transient network issues
- Cache previously discovered tools to maintain functionality when possible
- Provide clear error messages when a provider is unavailable
Tool Calling Errors:
- Validate inputs against the tool’s schema before calling
- Handle HTTP status codes and provider-specific errors
- Implement timeouts and retry logic for unreliable tools
try:
result = await client.call_tool("weather_api.get_weather", arguments={"location": "San Francisco"})
except UTCPProviderError as e:
print(f"Provider error: {e}")
except UTCPToolNotFoundError as e:
print(f"Tool not found: {e}")
except UTCPValidationError as e:
print(f"Invalid parameters: {e}")
Working with Different Provider Types
UTCP supports multiple provider types, and the client handles the protocol differences for you:
- HTTP providers: RESTful API calls
- WebSocket providers: Real-time bidirectional communication
- CLI providers: Local command-line tool execution
- SSE providers: Server-sent events for streaming responses
- Other provider types: Other provider types in the Providers section
The UTCPClient automatically uses the appropriate protocol based on its tool_provider
configuration.
Advanced Usage
Authentication
The UTCP client supports various authentication methods through provider configuration:
from utcp import UtcpClient
from utcp.shared.provider import HttpProvider
client = UtcpClient()
# API Key authentication
provider_with_api_key = HttpProvider(
name="weather_api",
provider_type="http",
url="https://api.example.com/utcp",
http_method="GET",
auth={
"auth_type": "api_key",
"api_key": "$YOUR_API_KEY",
"var_name": "X-API-Key"
}
)
# OAuth2 authentication
provider_with_oauth = HttpProvider(
name="translation_api",
provider_type="http",
url="https://translate.example.com/utcp",
http_method="GET",
auth={
"auth_type": "oauth2",
"client_id": "$YOUR_CLIENT_ID",
"client_secret": "$YOUR_CLIENT_SECRET",
"token_url": "https://auth.example.com/token"
}
)
# Register both providers
await client.register_tool_provider(provider_with_api_key)
await client.register_tool_provider(provider_with_oauth)
Client Configuration
For more complex applications, you can configure the UtcpClient using a configuration object. This allows you to externalize provider definitions and manage variables more effectively.
You can initialize the client by passing configuration parameters directly:
from utcp import UtcpClient
# Initialize client with configuration parameters
client = await UtcpClient.create(
providers_file_path="providers.json",
load_variables_from=[
{"type": "dotenv", "env_file_path": ".env"}
]
)
# Tools are loaded automatically from the providers file
await client.load_tools_from_providers()
Provider Configuration File (providers.json)
The providers_file_path
points to a JSON file containing a list of manual provider configurations. This allows you to manage your providers without hardcoding them in your application.
Example providers.json
:
[
{
"name": "weather_api",
"provider_type": "http",
"url": "https://api.example.com/utcp",
"http_method": "GET",
"auth": {
"auth_type": "api_key",
"api_key": "$WEATHER_API_KEY",
"var_name": "X-API-Key"
}
}
]
Variable Management and Substitution
The load_variables_from
option allows you to load variables from external sources, such as a .env
file. The client will automatically substitute variables in your provider configurations.
- Values prefixed with $ (like $API_KEY) or enclosed as ${VAR_NAME} will be replaced.
- The client looks for matching variables in the configuration and then in environment variables.
- If a variable isn’t found, the client raises an error.
Example .env
file:
WEATHER_API_KEY="your-secret-api-key"
This approach helps keep your secrets and other configuration values secure and separate from your codebase.
Working with Multiple Providers
You can register and use multiple providers in the same client:
# Register multiple providers
providers = [
HttpProvider(name="weather_api", provider_type="http", url="https://weather.example.com/utcp", http_method="GET"),
HttpProvider(name="translate_api", provider_type="http", url="https://translate.example.com/utcp", http_method="GET"),
HttpProvider(name="image_api", provider_type="http", url="https://image.example.com/utcp", http_method="GET")
]
for provider in providers:
tools = await client.register_tool_provider(provider)
print(f"Registered {len(tools)} tools from {provider.name}")
# Access tools from different providers
weather = await client.call_tool("weather_api.get_forecast", arguments={"location": "Tokyo"})
translation = await client.call_tool("translate_api.translate", arguments={"text": "Hello", "target": "fr"})
Deregistering Providers
You can deregister providers when they’re no longer needed:
# Deregister a provider by name
await client.deregister_tool_provider("weather_api")
Advanced Customization
The UtcpClient is designed to be extensible. For advanced use cases, you can replace its core components with your own custom implementations.
- Custom Tool Repositories: By default, the client stores tools in memory. If you need to persist discovered tools in a database or a file-based cache, you can implement a custom ToolRepository. Learn more in the Tool Repositories guide.
- Custom Tool Search Strategies: The default search strategy uses tag and description matching. If you need more sophisticated search capabilities, such as semantic search or integration with a vector database, you can implement a custom ToolSearchStrategy. Learn more in the Tool Search Strategies guide.